#include "msc_iap_user.h"
#include "string.h"
#include "ff.h"
#include "at32f403a_407_wk_config.h"


iap_info_type iap_info;
void (*pftarget)(void);
FATFS fs;
FIL file;
BYTE work[FF_MAX_SS];
#define PDRV_STR    "0:"
#define FILENAME    "0:/app.bin"
#define RENAME_SUCCESS_FILE  "0:/success.bin"
#define RENAME_ERROR_FILE  "0:/error.bin"


uint32_t flash_write_data(uint32_t address, uint8_t *data, uint32_t len);
error_status crc_cal(uint32_t addr, uint32_t n_byte, uint32_t file_crc);
uint32_t buffer_crc(uint8_t *buffer, uint32_t n_byte);
error_status update_upgrade_flag(error_status status);
void iap_set_upgrade_flag(void);
void iap_clear_upgrade_flag(void);


uint32_t crc32(uint8_t *data, uint16_t length)
{
    uint8_t i;
    uint32_t crc = 0xffffffff;        // Initial value
    while(length--)
    {
        crc ^= *data++;                // crc ^= *data; data++;
        for (i = 0; i < 8; ++i)
        {
            if (crc & 1)
                crc = (crc >> 1) ^ 0xEDB88320;// 0xEDB88320= reverse 0x04C11DB7
            else
                crc = (crc >> 1);
        }
    }
    return ~crc;
}

uint32_t crc32_mpeg_2(uint8_t *data, uint16_t length)
{
    uint8_t i;
    uint32_t crc = 0xffffffff;  // Initial value
    while(length--)
    {
        crc ^= (uint32_t)(*data++) << 24;// crc ^=(uint32_t)(*data)<<24; data++;
        for (i = 0; i < 8; ++i)
        {
            if ( crc & 0x80000000 )
                crc = (crc << 1) ^ 0x04C11DB7;
            else
                crc <<= 1;
        }
    }
    return crc;
}


error_status iap_check_upgrade(void)
{
  FRESULT ret;
  error_status status = SUCCESS;

  ret = f_mount(&fs, PDRV_STR, 1);
  if(ret == FR_OK)
  {
    ret = f_open(&file, FILENAME, FA_READ | FA_WRITE);
    if(ret != FR_OK)
    {
      status = ERROR;
    }
  }
  else
  {
    status = ERROR;
  }
  
  if(status == ERROR)
  {
    f_close(&file);
    f_unmount(PDRV_STR);
  }
  
  return status;
}


error_status iap_upgrade_process(void)
{
  FILINFO fno;
  FRESULT ret;
  error_status status = SUCCESS;
  uint32_t file_size = 0, read_sector = 0;
  uint32_t sector_num = 0;
  uint32_t rw_byte = 0;
  uint32_t address = iap_info.app_address, write_size = 0;
  uint32_t file_crc = 0;
  ret = f_stat(FILENAME, &fno);
  if(ret != FR_OK)
  {
    status = ERROR;
    goto UP_DONE;
  }
  if(fno.fsize <= 0 || fno.fsize > iap_info.flash_end_address - iap_info.app_address)
  {
    status = ERROR;
    goto UP_DONE;
  }
  
  file_size = fno.fsize;
  
  sector_num = file_size / 4096;
  if(file_size % 4096)
  {
    sector_num += 1;
  }
  
  crm_periph_clock_enable(CRM_CRC_PERIPH_CLOCK, TRUE);
  crc_data_reset();
  
  /* start upgrade */
  iap_clear_upgrade_flag();
  for(read_sector = 0; read_sector < sector_num; read_sector ++)
  {
    ret = f_read(&file,  iap_info.iap_fifo, 4096, &rw_byte);
    if(ret != FR_OK)
    {
      status = ERROR;
      goto UP_DONE;
    }
    if(write_size + rw_byte >= file_size)
    {
      flash_write_data(address, iap_info.iap_fifo, file_size - write_size);
      break;
    }
    else
    {
      flash_write_data(address, iap_info.iap_fifo, rw_byte);
      buffer_crc(iap_info.iap_fifo, rw_byte);
    }
    write_size += rw_byte;
    address += rw_byte;
  }
  
  file_crc = buffer_crc(iap_info.iap_fifo, file_size - write_size);
  
  /* crc check */
  if(crc_cal(iap_info.app_address, file_size, file_crc) != SUCCESS)
  {
    status = ERROR;
  }
UP_DONE:
  f_close(&file);
  f_unlink(RENAME_SUCCESS_FILE);
  f_unlink(RENAME_ERROR_FILE);
  if(status == SUCCESS)
  {
    ret = f_rename (FILENAME, RENAME_SUCCESS_FILE);
    iap_set_upgrade_flag();
  }
  else
  {
    f_rename (FILENAME, RENAME_ERROR_FILE);
  }
  
  f_unmount(PDRV_STR);
  return status;
}


uint32_t flash_write_data(uint32_t address, uint8_t *data, uint32_t len)
{
    uint32_t waddr = address;
    uint32_t wlength = len;
    uint32_t i_index = 0;
    if((address + len) > iap_info.flash_end_address )
    {
        return 1;
    }
    if((waddr & iap_info.sector_msk) == 0)
    {
        flash_unlock();
        if(len > iap_info.sector_size)
        {
            for(; wlength >= iap_info.sector_size; wlength -= iap_info.sector_size)
            {
                flash_sector_erase(waddr);
                waddr += iap_info.sector_size;
            }
            if(wlength > 0)
            {
                flash_sector_erase(waddr);
            }
        }
        else
        {
            flash_sector_erase(waddr);
        }
        flash_lock();
    }
    waddr = address;
    flash_unlock();
    for(i_index = 0; i_index < len; i_index ++)
    {
        flash_byte_program(waddr+i_index, data[i_index]);
    }
    flash_lock();
    return 0;
}

error_status crc_cal(uint32_t addr, uint32_t n_byte, uint32_t file_crc)
{
    uint32_t *paddr = (uint32_t *)addr;
    uint32_t wlen = n_byte / sizeof(uint32_t), remain = 0, remain_msk = 0xFFFFFFFF;
    uint32_t value, i_index = 0, flash_crc = 0;
    crm_periph_clock_enable(CRM_CRC_PERIPH_CLOCK, TRUE);
    crc_data_reset();

    /* flash crc */
    if(n_byte % sizeof(uint32_t) != 0)
    {
        remain = n_byte % sizeof(uint32_t);
    }

    for(i_index = 0; i_index < wlen; i_index ++)
    {
        value = *paddr;
        crc_one_word_calculate(value);
        paddr ++;
    }
    if(remain != 0)
    {
        value = *paddr;
        remain_msk <<= 8 * remain;
        value |= remain_msk;
        crc_one_word_calculate(value);
    }
    flash_crc = crc_data_get();

    if(flash_crc != file_crc)
    {
        return ERROR;
    }
    return SUCCESS;
}

uint32_t buffer_crc(uint8_t *buffer, uint32_t n_byte)
{
    uint32_t i_index = 0;
    uint32_t wlen = n_byte / sizeof(uint32_t), remain = 0, remain_msk = 0xFFFFFFFF;
    uint32_t *value = (uint32_t *)buffer;
    if(n_byte % sizeof(uint32_t) != 0)
    {
        remain = n_byte % sizeof(uint32_t);
    }
    for(i_index = 0; i_index < wlen; i_index ++)
    {
        crc_one_word_calculate(*value);
        value ++;
    }
    if(remain != 0)
    {
        remain_msk <<= 8 * remain;
        crc_one_word_calculate(*value | remain_msk);
    }
    return crc_data_get();
}


void iap_clear_upgrade_flag(void)
{
    flash_unlock();
    flash_sector_erase(iap_info.flag_address);
    flash_lock();
}

void iap_set_upgrade_flag(void)
{
    flash_unlock();
    flash_word_program(iap_info.flag_address, IAP_UPGRADE_COMPLETE_FLAG);
    flash_lock();
}

error_status iap_get_upgrade_flag(void)
{
    uint32_t flag_address = iap_info.flag_address;
    if(*((uint32_t *)flag_address) == IAP_UPGRADE_COMPLETE_FLAG)
    {
        return SUCCESS;
    }
    else
    {
        return ERROR;
    }
}

/**
  * @brief  jump to app
  * @param  none
  * @retval none
  */
void jump_to_app(uint32_t address)
{
    uint32_t stkptr, jumpaddr;
    stkptr = *(uint32_t *)address;
    jumpaddr = *(uint32_t *)(address + sizeof(uint32_t));

    /* disable nvic irq and periph clock, clear pending */
    nvic_irq_disable(USBFS_L_CAN1_RX0_IRQn);
    __NVIC_ClearPendingIRQ(USBFS_L_CAN1_RX0_IRQn);
    crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, FALSE);
    crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, FALSE);
    crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, FALSE);
    crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, FALSE);
    
    crm_periph_clock_enable(CRM_USB_PERIPH_CLOCK, FALSE);
    
    wk_periph_clock_disable_config();
    wk_nvic_disable_config();
    
    crm_periph_reset(CRM_USB_PERIPH_RESET, TRUE);
    crm_periph_reset(CRM_USB_PERIPH_RESET, FALSE);

    __set_MSP(stkptr);
    pftarget = (void (*) (void))jumpaddr;
    pftarget();
}

void iap_init(void)
{
    iap_info.flash_size = KB_TO_B(FLASH_SIZE_REG());

    iap_info.sector_size = SECTOR_SIZE_2K;
    iap_info.sector_msk = FLASH_SECTOR_2K_ALLGNED;
    

    iap_info.flash_start_address = FLASH_BASE;
    iap_info.flash_end_address = iap_info.flash_start_address + iap_info.flash_size;

    iap_info.app_address = FLASH_APP_ADDRESS;
    iap_info.flag_address = iap_info.app_address -  iap_info.sector_size;

    iap_info.fifo_length = 0;
    iap_info.iap_address = 0;
    
    
}
